package cn.wenhaha.app.data.service.imp;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.wenhaha.app.data.bean.DataPlugin;
import cn.wenhaha.app.data.bean.dto.PluginInfoDTO;
import cn.wenhaha.app.data.service.DataPluginService;
import cn.wenhaha.common.bean.PluginInfo;
import cn.wenhaha.common.bean.entity.Switch;
import cn.wenhaha.common.config.Global;
import cn.wenhaha.common.config.GlobalApp;
import cn.wenhaha.common.db.bean.SysDataSource;
import cn.wenhaha.common.db.bean.SysUser;
import cn.wenhaha.common.exception.ApiMSGException;
import cn.wenhaha.common.utils.PluginUtils;
import cn.wenhaha.common.utils.ValidatorUtil;
import cn.wenhaha.common.utils.file.FileUtils;
import cn.wenhaha.common.utils.spring.DynamicRouteUtils;
import cn.wenhaha.common.utils.spring.SpringUtils;
import cn.wenhaha.datasource.EventListen;
import cn.wenhaha.datasource.IDataSourcePlugin;
import org.pf4j.PluginManager;
import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.*;

/**
 * 数据源插件管理实现类
 * --------
 *
 * @author ：wyndem
 * @Date ：Created in 2022-07-10 13:07
 */
@Service
@Order(1)
public class DataPluginServiceImp implements DataPluginService, ApplicationRunner {

    @Resource
    private ValidatorUtil validatorUtil;

    @Resource
    private PluginUtils pluginUtils;
    // 插件模板路径
    private final String pathTemplate = Global.getRootPath() + File.separator + "templates" + File.separator + "plugins";
    private final String templateName = "template.zip";



    private static final Logger LOG = LoggerFactory.getLogger(DataPluginServiceImp.class);

    @Override
    public String addPlugin(MultipartFile file) {
        File libFile = null;
        String id = null;
        try {
            InputStream inputStream = file.getInputStream();
            if (!FileUtils.isJar(inputStream)) {
                throw ApiMSGException.build(1008);
            }
            // 查找插件信息
            String path = PluginUtils.getDataSourcePluginPath() + File.separator + file.getOriginalFilename();
            libFile = FileUtil.touch(new File(path));
            file.transferTo(libFile);
            IoUtil.close(inputStream);

            PluginInfo info = Optional.ofNullable(pluginUtils.info(path))
                    .orElseThrow(() -> ApiMSGException.build(1009));
            Map<String, Object> attr = info.getAttr();
            PluginInfoDTO pluginInfoDTO = new PluginInfoDTO();
            BeanUtil.copyProperties(info, pluginInfoDTO, true);
            pluginInfoDTO.setCode(info.getId());
            pluginInfoDTO.setAddPage(MapUtil.getStr(attr, "Plugin-Add-Page"));
            pluginInfoDTO.setUpdatePage(MapUtil.getStr(attr, "Plugin-Update-Page"));
            validatorUtil.validate(pluginInfoDTO);

            SysDataSource dbSysDataSource = SysDataSource.dao.findByCode(pluginInfoDTO.getCode());
            if (dbSysDataSource != null) {
                throw ApiMSGException.build(-1010, StrUtil.format("当前插件已安装，或code冲突,冲突插件名为：{} 版本： {}", dbSysDataSource.getName(), dbSysDataSource.getVersion()));
            }
            id = pluginUtils.loadAndStart(path);

            IDataSourcePlugin obj = Optional.ofNullable(pluginUtils.getObject(IDataSourcePlugin.class, id))
                    .orElseThrow(() -> ApiMSGException.build(7006));

            EventListen eventListen = Optional.ofNullable(obj.getEventListen()).orElseThrow(() -> ApiMSGException.build(7007));

            try {
                eventListen.onLoad(info.getId());
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("插件OnLoad事件异常，信息为：" + e.getMessage());
            }


            String fileName = info.getName() + "_" + info.getVersion() + "_" + info.getAuthor() + "_" + System.currentTimeMillis() + ".jar";
            path = PluginUtils.getDataSourcePluginPath() + File.separator + fileName;


            dbSysDataSource = new SysDataSource();
            dbSysDataSource.put(BeanUtil.beanToMap(pluginInfoDTO));
            dbSysDataSource.setId(info.getId());
            dbSysDataSource.setPath(path);
            dbSysDataSource.setStatus(Switch.OFF.getCode().toString());
            dbSysDataSource.setFileName(fileName);
            dbSysDataSource.setCreateTime(DateUtil.now());
            dbSysDataSource.setAddPage(pluginInfoDTO.getAddPage());
            dbSysDataSource.setUpdatePage(pluginInfoDTO.getUpdatePage());
            dbSysDataSource.setCreateBy(StpUtil.getSession().getModel("info", SysUser.class).getUserName());
            dbSysDataSource.save();

            FileUtil.move(libFile, new File(path), true);
            return dbSysDataSource.getId();
        } catch (Exception e) {
            e.printStackTrace();
            Integer code = -1;
            if (e instanceof ApiMSGException) {
                code = ((ApiMSGException) e).getCode();
            }
            throw ApiMSGException.build(code, e.getMessage());
        } finally {
            if (StrUtil.isNotEmpty(id)) {
                pluginUtils.delete(id);
            }
            FileUtil.del(libFile);
        }
    }


    @Override
    public String update(MultipartFile file) {
        File libFile = null;

        try {
            InputStream inputStream = file.getInputStream();
            if (!FileUtils.isJar(inputStream)) {
                throw ApiMSGException.build(1008);
            }
            // 查找插件信息
            String name = FileUtil.mainName(file.getOriginalFilename()) + "_" + RandomUtil.randomString(5) + "." + FileUtil.extName(file.getOriginalFilename());

            String path = PluginUtils.getDataSourcePluginPath() + File.separator + name;
            libFile = FileUtil.touch(new File(path));
            file.transferTo(libFile);
            IoUtil.close(inputStream);


            PluginInfo info = Optional.ofNullable(pluginUtils.info(path))
                    .orElseThrow(() -> ApiMSGException.build(1009));
            Map<String, Object> attr = info.getAttr();
            PluginInfoDTO pluginInfoDTO = new PluginInfoDTO();
            BeanUtil.copyProperties(info, pluginInfoDTO, true);
            pluginInfoDTO.setCode(info.getId());
            pluginInfoDTO.setAddPage(MapUtil.getStr(attr, "Plugin-Add-Page"));
            pluginInfoDTO.setUpdatePage(MapUtil.getStr(attr, "Plugin-Update-Page"));
            validatorUtil.validate(pluginInfoDTO);

            SysDataSource dbDataSource = Optional.ofNullable(SysDataSource.dao.findById(info.getId()))
                    .orElseThrow(() -> ApiMSGException.build(7004));
            if ((StrUtil.equals(Switch.ON.getCode().toString(), dbDataSource.getStatus())) ||
                    (pluginUtils.containPluginId(dbDataSource.getId()) && pluginUtils.status(dbDataSource.getId()) == PluginState.STARTED)
            ) {
                throw ApiMSGException.build(7005);
            }
            String fileName = info.getName() + "_" + info.getVersion() + "_" + info.getAuthor() + "_" + System.currentTimeMillis() + ".jar";
            path = PluginUtils.getDataSourcePluginPath() + File.separator + fileName;
            FileUtil.move(libFile, new File(path), true);

            // 删除原来的前端模板
            String targetPath = pathTemplate + File.separator + dbDataSource.getId();
            if (FileUtil.exist(targetPath)) {
                FileUtil.del(targetPath);
            }
            FileUtil.del(dbDataSource.getPath());
            dbDataSource.setPath(path);
            dbDataSource.setFileName(file.getOriginalFilename());
            dbDataSource._setOrPut(BeanUtil.beanToMap(pluginInfoDTO));
            dbDataSource.setAddPage(pluginInfoDTO.getAddPage());
            dbDataSource.setUpdatePage(pluginInfoDTO.getUpdatePage());
            dbDataSource.setUpdateTime(DateUtil.now());
            SysUser infoUser = StpUtil.getSession().getModel("info", SysUser.class);
            dbDataSource.setUpdateBy(infoUser.getUserName());
            dbDataSource.update();
            return dbDataSource.getId();
        } catch (IOException e) {
            e.printStackTrace();
            throw ApiMSGException.build(-1, e.getMessage());
        } finally {
            FileUtil.del(libFile);
        }

    }

    @Override
    public void enable(SysDataSource sysDataSource) {
        if (!FileUtil.exist(sysDataSource.getPath())) {
            throw new ApiMSGException(-1, "插件路径不存在 " + sysDataSource.getPath());
        } else if (pluginUtils.containPluginId(sysDataSource.getId())) {
            throw new ApiMSGException(-1, "该插件已经启用了");
        }

        LOG.info("正在加载【{}】数据源插件", sysDataSource.getName());
        String id = pluginUtils.loadAndStart(sysDataSource.getPath());

        File touch = null;
        String targetPath = null;

        try {
            IDataSourcePlugin obj = pluginUtils.getObject(IDataSourcePlugin.class, id);
            ClassLoader classLoader = obj.getClass().getClassLoader();
            InputStream resourceAsStream = classLoader.getResourceAsStream(templateName);
            if (resourceAsStream != null) {
                // 如何拷贝资源文件
                targetPath = pathTemplate + File.separator + sysDataSource.getId();
                if (!FileUtil.exist(targetPath)) {
                    touch = FileUtil.touch(targetPath + File.separator + templateName);
                    OutputStream outputStream = Files.newOutputStream(touch.toPath());
                    IoUtil.copy(resourceAsStream, outputStream);
                    IoUtil.close(outputStream);
                    IoUtil.close(resourceAsStream);
                    ZipUtil.unzip(touch.getPath(), targetPath);
                }
            }

            EventListen eventListen = Optional.ofNullable(obj.getEventListen()).orElseThrow(() -> ApiMSGException.build(7007));
            eventListen.onStart(sysDataSource.getId());
            Class<?>[] controller = obj.controller();

            try {
                Arrays.stream(controller).forEach(SpringUtils::registerBean);
                Arrays.stream(controller).forEach((c) -> DynamicRouteUtils.registerController(c, "plugin/" + sysDataSource.getId()));
            } catch (Exception e) {
                e.printStackTrace();
                Arrays.stream(controller).forEach(DynamicRouteUtils::unregisterMapping);
                Arrays.stream(controller).forEach(SpringUtils::removeBean);
                throw ApiMSGException.build(-1, e.getMessage());
            }
            sysDataSource.setStatus(Switch.ON.getCode().toString());
            sysDataSource.update();
        } catch (Exception e) {
            pluginUtils.unload(sysDataSource.getCode());
            FileUtil.del(targetPath);
            e.printStackTrace();
            throw ApiMSGException.build(-1, e.getMessage());
        } finally {
            FileUtil.del(touch);
        }


    }


    private void disabled(SysDataSource sysDataSource, boolean delete) {
        String id = sysDataSource.getId();
        PluginState status = pluginUtils.status(id);
        if (status != PluginState.STARTED) {
            return;
        }
        pluginUtils.checkStop(id);
        try {
            IDataSourcePlugin obj = pluginUtils.getObject(IDataSourcePlugin.class,id);

            EventListen eventListen = Optional.ofNullable(obj.getEventListen())
                    .orElseThrow(() -> ApiMSGException.build(7007));
            eventListen.onStop();
            if (delete) {
                eventListen.unLoad();
            }

            String targetPath = pathTemplate + File.separator + id;
            if (FileUtil.exist(targetPath)) {
                FileUtil.del(targetPath);
            }
            Class<?>[] controller = obj.controller();
            Arrays.stream(controller).forEach(DynamicRouteUtils::unregisterMapping);
            Arrays.stream(controller).forEach(SpringUtils::removeBean);
            if (delete) {
                pluginUtils.delete(id);
            } else {
                pluginUtils.unload(id);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }

    }


    @Override
    public void disabled(SysDataSource sysDataSource) {
        disabled(sysDataSource, false);
        sysDataSource.setStatus(Switch.OFF.getCode().toString());
        sysDataSource.update();
    }


    @Override
    public void remove(String id) {
        SysDataSource sysDataSource = Optional.ofNullable(SysDataSource.dao.findById(id))
                .orElseThrow(() -> ApiMSGException.build(7009));

        disabled(sysDataSource, true);

        FileUtil.del(sysDataSource.getPath());
        sysDataSource.delete();
    }



    /**
     * <p>
     *    获取当前已激活的所有数据源插件
     * </p>
     * @Author: Wyndem
     * @DateTime: 2023-01-25 15:30
     */
    public List<DataPlugin> getDataPlugins() {
        PluginManager manager = pluginUtils.getManager();

        List<IDataSourcePlugin> extensions = manager.getExtensions(IDataSourcePlugin.class);

        ArrayList<DataPlugin> dataPlugins = new ArrayList<>(extensions.size());
        for (IDataSourcePlugin s : extensions){
            PluginWrapper pluginWrapper = manager.whichPlugin(s.getClass());
            DataPlugin build = DataPlugin.build(SysDataSource.dao.findById(pluginWrapper.getPluginId()), s);
            dataPlugins.add(build);
        }

        return dataPlugins;
    }



    @Override
    public IDataSourcePlugin getPluginId(String id) {
        if (StrUtil.isEmpty(id)) {
            throw ApiMSGException.build(7010);
        }

        IDataSourcePlugin plugin =  pluginUtils.getObject(IDataSourcePlugin.class, id);
        if (plugin == null) {
            throw ApiMSGException.build(7009);
        }

        return plugin;
    }


    @Override
    public void run(ApplicationArguments args) throws Exception {

        if (!GlobalApp.isLoadConfigDb()) {
            LOG.debug("数据暂末配置，不加载数据插件");
            return;
        }
        // 获取已启用的插件原
        List<SysDataSource> sysDataSources = SysDataSource.dao.enableList();
        if (sysDataSources != null && sysDataSources.size() > 0) {
            LOG.info("正在加载数据源插件");
            sysDataSources.forEach(this::enable);
        }
    }


}
